import mx.utils.Delegate;

import flash.geom.Point;
import flash.geom.Rectangle;

import gugga.animations.AnimationSubjectCacheAsBitmapPolicy;
import gugga.animations.IAnimation;
import gugga.animations.MaskAnimationTypes;
import gugga.animations.MotionDirections;
import gugga.animations.MotionTypes;
import gugga.animations.PropertiesTweenAnimation;
import gugga.animations.PropertiesTweenAnimationType;
import gugga.events.EventDispatcher;
import gugga.utils.DoLaterUtil;
import gugga.utils.EventRethrower;

/**
 * @author Todor Kolev
 */
class gugga.animations.ScrollRectAnimation extends EventDispatcher implements IAnimation 
{
	//TODO: should add property - UseCurrentScrollRectAsInitialForAmimation 
	
	private var mIsInterupting : Boolean = false;
	
	private var mType : MaskAnimationTypes;
	private var mDirection : MotionDirections;
	private var mMotionType : MotionTypes;
	
	private var mSubjectInitialVisible : Boolean;
	private var mSubjectInitialPosition : Point;
	private var mAnimationRect : Rectangle;
	
	private var mScrollRect : Rectangle;
	private var mTweenAnimation : PropertiesTweenAnimation;
	
	private var mSubject : MovieClip;
	public function get Subject() : MovieClip { return mSubject; }
	public function set Subject(aValue:MovieClip) : Void { mSubject = aValue; }
	
	public function get EasingEquation() : Function 
	{ 
		return mTweenAnimation.EasingEquation; 
	}
	public function set EasingEquation(aValue:Function) : Void 
	{ 
		mTweenAnimation.EasingEquation = aValue; 
	}
	
	//TODO: Rename
	public function get TweenTime() : Number 
	{ 
		return mTweenAnimation.TweenTime; 
	}
	public function set TweenTime(aValue:Number) : Void 
	{ 
		mTweenAnimation.TweenTime = aValue;
	}
	
	public function get TweenType() : PropertiesTweenAnimationType 
	{ 
		return mTweenAnimation.TweenType; 
	}
	public function set TweenType(aValue:PropertiesTweenAnimationType) : Void 
	{ 
		mTweenAnimation.TweenType = aValue;
	}
	
	public function get SubjectCacheAsBitmapPolicy() : AnimationSubjectCacheAsBitmapPolicy 
	{ 
		return mTweenAnimation.SubjectCacheAsBitmapPolicy; 
	}
	public function set SubjectCacheAsBitmapPolicy(aValue:AnimationSubjectCacheAsBitmapPolicy) : Void 
	{ 
		mTweenAnimation.SubjectCacheAsBitmapPolicy = aValue; 
	}
	
	
	private function ScrollRectAnimation(aSubject : MovieClip, aType : MaskAnimationTypes, 
		aDirection : MotionDirections, aMotionType : MotionTypes) 
	{
		super();
		
		mSubject = aSubject;
		mType = aType;
		mDirection = aDirection;
		mMotionType = aMotionType;
		
		mTweenAnimation = new PropertiesTweenAnimation();
		mTweenAnimation.TweenUpdateDelegate = Delegate.create(this, onTweenUpdated);
		mTweenAnimation.addEventListener("completed", Delegate.create(this, onTweenAnimationCompleted));
		
		//set default to force true
		SubjectCacheAsBitmapPolicy = AnimationSubjectCacheAsBitmapPolicy.ForceTrueWhileAnimating;
	}

	public function addCuePoint(aPosition : Number, aEventName : String) : Void 
	{
		mTweenAnimation.addCuePoint(aPosition, aEventName);
		EventRethrower.create(this, mTweenAnimation, aEventName);
	
	}
	
	public function setBoundsAnimationRect() : Void
	{
		var subjectBounds : Object = mSubject.getBounds(mSubject);
		mAnimationRect = new Rectangle(
			subjectBounds["xMin"], 
			subjectBounds["yMin"],
			subjectBounds["xMax"] - subjectBounds["xMin"], 
			subjectBounds["yMax"] - subjectBounds["yMin"]
		);
	}

	public function setNaturalAnimationRect() : Void
	{
		mAnimationRect = new Rectangle(
			0, 
			0,
			mSubject._width, 
			mSubject._height
		);
	}

	public function setCustomAnimationRect(aRect : Rectangle) : Void
	{
		mAnimationRect = aRect.clone();
	}

	public function start() : Void 
	{
		mIsInterupting = false;
		
		dispatchEvent({type: "start", target: this});
		
		mSubjectInitialPosition = new Point(mSubject._x, mSubject._y);
		
		if(!mAnimationRect)
		{
			setNaturalAnimationRect();
		}
		
		mScrollRect = getStartingScrollRect();
		mTweenAnimation.Subject = mScrollRect;
		mSubject.scrollRect = mScrollRect;
		
		//TODO: revise! can be removed
		mSubject._visible = true;
		
		mSubjectInitialVisible = mSubject._visible;
		
		var targetScrollRect : Rectangle = getTargetScrollRect();
		
		mTweenAnimation.TweeningProprties = ["x", "y", "width", "height"];
		mTweenAnimation.TweenToValues = 
			[targetScrollRect.x, targetScrollRect.y, targetScrollRect.width, targetScrollRect.height];
		
		mSubject._y = mSubjectInitialPosition.y + mAnimationRect.y;
		mSubject._x = mSubjectInitialPosition.x + mAnimationRect.x;
		
		mTweenAnimation.start();
	}
	
	private function getStartingScrollRect() : Rectangle
	{
		var result : Rectangle = mAnimationRect.clone();
		
		if(mMotionType == MotionTypes.In)
		{
			result = transformRectByAnimationTypeAndDirection(result);
		}
		
		return result;
	}
	
	private function getTargetScrollRect() : Rectangle
	{
		var result : Rectangle = mAnimationRect.clone();
		
		if(mMotionType == MotionTypes.Out)
		{
			result = transformRectByAnimationTypeAndDirection(result);
		}
		
		return result;
	}
	
	private function transformRectByAnimationTypeAndDirection(aRect : Rectangle) : Rectangle
	{
		var direction : MotionDirections; 
			
		if(mType == MaskAnimationTypes.Fly)
		{
			direction = mDirection;
		}
		else
		{
			direction = invertDirection(mDirection);
		}
		
		if (direction != MotionDirections.Up &&
			direction != MotionDirections.Down)
		{
			aRect.width = 0;
		}
		
		if (direction != MotionDirections.Left &&
			direction != MotionDirections.Right)
		{
			aRect.height = 0;
		}
		
		if (direction == MotionDirections.Up ||
			direction == MotionDirections.UpLeft ||
			direction == MotionDirections.UpRight)
		{
			aRect.y = mAnimationRect.y + mAnimationRect.height;
		}
		
		if (direction == MotionDirections.Left ||
			direction == MotionDirections.UpLeft ||
			direction == MotionDirections.DownLeft)
		{
			aRect.x = mAnimationRect.x + mAnimationRect.width;
		}
		
		return aRect;
	}
	
	private function invertDirection(aDirection : MotionDirections) : MotionDirections
	{
		switch(aDirection)
		{
			case MotionDirections.Left:
				return	MotionDirections.Right;
			case MotionDirections.Right:
				return	MotionDirections.Left;
			case MotionDirections.Down:
				return	MotionDirections.Up;
			case MotionDirections.DownLeft:
				return	MotionDirections.UpRight;
			case MotionDirections.DownRight:				
				return	MotionDirections.UpLeft;	
			case MotionDirections.Up:
				return	MotionDirections.Down;
			case MotionDirections.UpRight:
				return	MotionDirections.DownLeft;
			case MotionDirections.UpLeft:				
				return	MotionDirections.DownRight;
		}
	}

	public function isRunning() : Boolean 
	{
		return mTweenAnimation.isRunning();
	}

	public function isImmediatelyInterruptable() : Boolean 
	{
		return true;
	}

	public function interrupt() : Void 
	{
		mIsInterupting = true;
		
		mTweenAnimation.interrupt();	
		dispose();
		
		//TODO: revise commented code
		//if(mMotionType == MotionTypes.Out)
		//{
		//	mSubject._visible = false;
		//}
		
		DoLaterUtil.doLater(this, interrupted, null, 1);
	}
	
	private function interrupted() : Void
	{ 
		//TODO: revise commented code
		//if(mMotionType == MotionTypes.Out)
		//{
		//	mSubject._visible = mSubjectInitialVisible;
		//}
		
		mIsInterupting = false;
		dispatchEvent({type: "interrupted", target: this});
	}
	
	private function onTweenUpdated(ev) : Void 
	{	
		mScrollRect.x = Math.round(mScrollRect.x);
		mScrollRect.y = Math.round(mScrollRect.y);
		mScrollRect.width = Math.round(mScrollRect.width);
		mScrollRect.height = Math.round(mScrollRect.height);
		
		mSubject.scrollRect = mScrollRect;
		
		if (mDirection == MotionDirections.Down ||
			mDirection == MotionDirections.DownLeft ||
			mDirection == MotionDirections.DownRight)
		{
			if(mType == MaskAnimationTypes.Wipe)
			{
				mSubject._y = mSubjectInitialPosition.y + mScrollRect.y;
			}
			else
			{
				mSubject._y = mSubjectInitialPosition.y + 
					mAnimationRect.y + mAnimationRect.height - mScrollRect.height;
			}
		}
		
		if (mDirection == MotionDirections.Right ||
			mDirection == MotionDirections.UpRight ||
			mDirection == MotionDirections.DownRight)
		{
			if(mType == MaskAnimationTypes.Wipe)
			{
				mSubject._x = mSubjectInitialPosition.x + mScrollRect.x;
			}
			else
			{
				mSubject._x = mSubjectInitialPosition.x + 
					mAnimationRect.x + mAnimationRect.width - mScrollRect.width;
			}
		}
	}
	
	private function onTweenAnimationCompleted() : Void 
	{
		dispose();
		
		if(mMotionType == MotionTypes.Out)
		{
			mSubject._visible = false;
			DoLaterUtil.doLater(this, completed, null, 1);
		}
		else
		{
			completed();
		}
	}
	
	private function completed() : Void
	{ 
		if(mMotionType == MotionTypes.Out)
		{
			mSubject._visible = mSubjectInitialVisible;
		}
		
		dispatchEvent({type: "completed", target: this});
	}
	
	private function dispose() : Void
	{
		mSubject._y = mSubjectInitialPosition.y;
		mSubject._x = mSubjectInitialPosition.x;
		
//		DebugUtils.trace("* dispose scroll rect");
		
		mSubject.scrollRect = null;
	}
	
	//-----------------------------------------------------------------------------
	
	public static function createWipeInFrameAnimation(aDirection : MotionDirections, 
		aTarget : MovieClip, aDuration : Number, aEasingType : Function) : ScrollRectAnimation
	{
		var result : ScrollRectAnimation = new ScrollRectAnimation(aTarget, MaskAnimationTypes.Wipe,
			aDirection, MotionTypes.In);
		
		result.TweenType = PropertiesTweenAnimationType.FrameTween;
		applyAdditionalProperties(result, aDuration, aEasingType);
		
		return result;
	}
	
	public static function createWipeOutFrameAnimation(aDirection : MotionDirections, 
		aTarget : MovieClip, aDuration : Number, aEasingType : Function) : ScrollRectAnimation
	{
		var result : ScrollRectAnimation = new ScrollRectAnimation(aTarget, MaskAnimationTypes.Wipe,
			aDirection, MotionTypes.Out);
		
		result.TweenType = PropertiesTweenAnimationType.FrameTween;
		applyAdditionalProperties(result, aDuration, aEasingType);
		
		return result;
	}
	
	public static function createFlyInFrameAnimation(aDirection : MotionDirections, 
		aTarget : MovieClip, aDuration : Number, aEasingType : Function) : ScrollRectAnimation
	{
		var result : ScrollRectAnimation = new ScrollRectAnimation(aTarget, MaskAnimationTypes.Fly,
			aDirection, MotionTypes.In);
		
		result.TweenType = PropertiesTweenAnimationType.FrameTween;
		applyAdditionalProperties(result, aDuration, aEasingType);
		
		return result;
	}
	
	public static function createFlyOutFrameAnimation(aDirection : MotionDirections, 
		aTarget : MovieClip, aDuration : Number, aEasingType : Function) : ScrollRectAnimation
	{
		var result : ScrollRectAnimation = new ScrollRectAnimation(aTarget, MaskAnimationTypes.Fly,
			aDirection, MotionTypes.Out);
		
		result.TweenType = PropertiesTweenAnimationType.FrameTween;
		applyAdditionalProperties(result, aDuration, aEasingType);
		
		return result;
	}
	
	public static function createWipeInTimeAnimation(aDirection : MotionDirections, 
		aTarget : MovieClip, aDuration : Number, aEasingType : Function) : ScrollRectAnimation
	{
		var result : ScrollRectAnimation = new ScrollRectAnimation(aTarget, MaskAnimationTypes.Wipe,
			aDirection, MotionTypes.In);
		
		result.TweenType = PropertiesTweenAnimationType.TimeTween;
		applyAdditionalProperties(result, aDuration, aEasingType);
		
		return result;
	}
	
	public static function createWipeOutTimeAnimation(aDirection : MotionDirections, 
		aTarget : MovieClip, aDuration : Number, aEasingType : Function) : ScrollRectAnimation
	{
		var result : ScrollRectAnimation = new ScrollRectAnimation(aTarget, MaskAnimationTypes.Wipe,
			aDirection, MotionTypes.Out);
		
		result.TweenType = PropertiesTweenAnimationType.TimeTween;
		applyAdditionalProperties(result, aDuration, aEasingType);
		
		return result;
	}
	
	public static function createFlyInTimeAnimation(aDirection : MotionDirections, 
		aTarget : MovieClip, aDuration : Number, aEasingType : Function) : ScrollRectAnimation
	{
		var result : ScrollRectAnimation = new ScrollRectAnimation(aTarget, MaskAnimationTypes.Fly,
			aDirection, MotionTypes.In);
		
		result.TweenType = PropertiesTweenAnimationType.TimeTween;
		applyAdditionalProperties(result, aDuration, aEasingType);
		
		return result;
	}
	
	public static function createFlyOutTimeAnimation(aDirection : MotionDirections, 
		aTarget : MovieClip, aDuration : Number, aEasingType : Function) : ScrollRectAnimation
	{
		var result : ScrollRectAnimation = new ScrollRectAnimation(aTarget, MaskAnimationTypes.Fly,
			aDirection, MotionTypes.Out);
		
		result.TweenType = PropertiesTweenAnimationType.TimeTween;
		applyAdditionalProperties(result, aDuration, aEasingType);
		
		return result;
	}
	
	private static function applyAdditionalProperties(aAnimation : ScrollRectAnimation, aDuration : Number, 
		aEasingType : Function) : Void
	{
		if(aDuration)
		{
			aAnimation.TweenTime = aDuration;
		}
		
		if(aEasingType)
		{
			aAnimation.EasingEquation = aEasingType;
		}
	}
}
